#pragma once

#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <fstream>
#include <cstdlib>
#include <unordered_map>
// 根据题目list文件，加载所有的题目信息到内存中
// model：主要用来和数据进行交互，对外提供访问数据接口

namespace ns_model {
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;
    //题目的相关信息节点
    struct Question {
        string number; // 题目的编号
        string title;  // 题目的标题
        string star;   // 难度：简单/中等/困难
        int cpu_limit; // 题目的时间要求
        int mem_limit; // 题目的空间要求
        string desc;   // 题目的描述
        string header; // 题目预设给用户在线编辑的代码
        string tail;   // 题目的测试用例，需要和header拼接，形成完整代码
    };

    const string questions_list = "./questions/questions.list"; //题目列表的路径
    const string question_path = "./questions/"; //题库路径

    class Model {
    private:
        // kv ---> k:题号 v:题目细节
        unordered_map<string, Question> questions;
    public:
        Model() {
            assert(LoadQuestionList(questions_list));
        }

        // 加载配置文件：questions/questions.list + 题目编号文件
        bool LoadQuestionList(const string &question_list) {
            ifstream in(question_list);//打开配置文件
            if (!in.is_open()) {
                LOG(FATAL) << "加载题库失败，请检查是否存在题库文件" << "\n";
                return false;
            }

            string line;
            //按行读取题目列表的路径中的内容
            while (getline(in, line)) {
                //题目列表的路径的内容如：
                // 1 判断回文数 简单 1 30000
                // 2 找出最大值 简单 1 30000
                vector<string> tokens;//保存切分的子串：如：vector{1，判断回文数，简单，1，30000}
                StringUtil::SplitString(line, &tokens, " ");//按空格进行切分
                if (tokens.size() != 5) {
                    LOG(WARNING) << "加载部分题目失败，请检查文件格式" << "\n";
                    continue;
                }
                //填充Question：
                Question q;
                q.number = tokens[0]; //填写题目编号：1
                q.title = tokens[1];  //填写题目标题：判断回文数
                q.star = tokens[2];   //填写题目难度：简单
                q.cpu_limit = atoi(tokens[3].c_str()); //填写cpu限制：1
                q.mem_limit = atoi(tokens[4].c_str()); //填写内存限制：30000

                //拼接题目路径
                //题库路径在 questions/ 如：
                // questions/
                //      1/
                //      2/
                string _path = question_path; //_path:题目路径
                _path += q.number;
                _path += "/";

                FileUtil::ReadFile(_path + "desc.txt", &(q.desc), true);    //读取：题目的描述路径（true表示需要换行）
                FileUtil::ReadFile(_path + "header.cpp", &(q.header), true);//读取：题目预设给用户在线编辑的代码的路径
                FileUtil::ReadFile(_path + "tail.cpp", &(q.tail), true);    //读取：题目的测试用例路径
                questions.insert({q.number, q});//插入到unordered_map中
            }

            LOG(INFO) << "加载题库......成功" << "\n";
            in.close();
            return true;
        }

        //获取所有题目
        bool GetAllQuestions(vector<Question> *out) {
            //如果没有题目
            if (questions.size() == 0) {
                LOG(ERROR) << "用户获取题库失败" << "\n";
                return false;
            }
            //遍历questions（题目和题目细节的映射）放到vector中
            for (const auto &q: questions) {
                out->push_back(q.second);
            }
            return true;
        }

        //获取一道题目
        bool GetOneQuestion(const string &number, Question *q) {
            const auto &iter = questions.find(number);//去映射表中查找对应的题目
            if (iter == questions.end()) {
                LOG(ERROR) << "用户获取题目失败，题目编号：" << number << "\n";
                return false;
            }
            (*q) = iter->second;
            return true;
        }

        ~Model() {}
    };
}
